﻿
using System;
using System.Windows;
using Dw = System.Drawing;
using Agora.Rtc;
using SharpDX;
using SharpDX.Direct3D9;
using System.Windows.Interop;
using System.Windows.Forms;
using System.Windows.Media;
using System.Drawing;
using MyMeeting.CustomRender;

namespace MyMeeting.src.Advanced.CustomRender
{
    public class SharpDximgFormRender : BaseRender
    {
        // render
        private bool disposing_ = false;
        private IntPtr handle_ = IntPtr.Zero;
        private Dw.Size size_ = new Dw.Size();
        private DataType data_type_ = DataType.kYUV420;

        // d3d
        private SharpDX.Direct3D9.Direct3D d3d_ = new SharpDX.Direct3D9.Direct3D();
        private SharpDX.Direct3D9.Device device = null;
        private SharpDX.Direct3D9.Surface _offScreenSurface = null;
        private Texture texture;
        private SharpDX.Direct3D9.Surface d3d_backend_surface_ = null;
        private SharpDX.Direct3D9.Font d3d_fps_font_ = null;
        private Dw.Size offScrrenBufferSize = new Dw.Size();
        private Dw.Size backSurfaceBufferSize = new Dw.Size();

        // WPF D3DImage
        private D3DImage _d3dImage;
        private Form dummyWindow;
        private int imgW = 0, imgH = 0;

        public SharpDximgFormRender(D3DImage img)
        {
            this.dummyWindow = new Form();
            this.handle_ = this.dummyWindow.Handle;
            this.dummyWindow.Width = 320;
            this.dummyWindow.Width = 180;

            this._d3dImage = img;
            _d3dImage.Dispatcher.Invoke(new Action(() =>
            {
                _d3dImage.IsFrontBufferAvailableChanged += D3DImage_IsFrontBufferAvailableChanged;
            }));
            

        }

        public override void SetD3dImag(D3DImage img)
        {
           
           
        }

        #region override BaseRender
        public override void Dispose()
        {
            if (disposing_ == true)
                return;

            disposing_ = true;

            StopFpsTimer();

            if (d3d_fps_font_ != null)
                d3d_fps_font_.Dispose();

            if (_offScreenSurface != null)
                _offScreenSurface.Dispose();

            if (d3d_backend_surface_ != null)
                d3d_backend_surface_.Dispose();

            if (device != null)
                device.Dispose();

            d3d_.Dispose();
            this.SafeRelease(this.dummyWindow);
            this.SafeRelease(this.texture);
        }

        private void SafeRelease(IDisposable item)
        {
            try
            {
                if (item != null)
                {
                    item.Dispose();
                }
            }
            catch
            {
            }
        }

        public override CustomVideoBoxRenderType GetRenderType()
        {
            return data_type_ == DataType.kBGRA ? CustomVideoBoxRenderType.kSharpDX_BGRA : CustomVideoBoxRenderType.kSharpDX_YUV420;
        }

        public override bool Initialize(IntPtr handle, Dw.Size size, DataType dataType)
        {
            size_ = size;
          
            data_type_ = dataType;
           // this.handle_ = handle;
            if (!AutoCreateD3DDevice(1920, 1080, size.Width, size.Height))
            {
                return false;
            }

           // StartFpsTimer();

            return true;
        }

        private PresentParameters GetPresentParameters(int width, int height, Format format)
        {
            PresentParameters presentParams = new PresentParameters();
            presentParams.PresentFlags = PresentFlags.Video;
            presentParams.Windowed = true;
            presentParams.DeviceWindowHandle = this.handle_;
            presentParams.BackBufferWidth = width == 0 ? 1 : width;
            presentParams.BackBufferHeight = height == 0 ? 1 : height;
            presentParams.SwapEffect = SwapEffect.Discard;
            presentParams.PresentationInterval = PresentInterval.Immediate;
            presentParams.BackBufferFormat = format;
            presentParams.BackBufferCount = 1;
            presentParams.EnableAutoDepthStencil = false;

            return presentParams;
        }

        public override void UpdateSize(Dw.Size size)
        {
            size_ = size;
        }

        public override void DeliverFrame(VideoFrame videoFrame)
        {
            if (disposing_) return;

            if (data_type_ == DataType.kBGRA && videoFrame.type != VIDEO_PIXEL_FORMAT.VIDEO_PIXEL_BGRA && videoFrame.type != VIDEO_PIXEL_FORMAT.VIDEO_PIXEL_RGBA)
                return;

            if (data_type_ == DataType.kYUV420 && videoFrame.type != VIDEO_PIXEL_FORMAT.VIDEO_PIXEL_I420)
                return;

            if (AutoCreateD3DDevice(videoFrame.width, videoFrame.height, size_.Width, size_.Height) != true)
            {
                Console.WriteLine("d3d failed to create d3d device when deliver frame");
                return;
            }

            #region lock offscreen surface
            SharpDX.DataRectangle locked_data = new DataRectangle();

            const int MAX_TRY = 3;
            int times = 0;
            do
            {
                bool need_retry = false;
                try
                {
                    locked_data = _offScreenSurface.LockRectangle(SharpDX.Direct3D9.LockFlags.DoNotWait);
                }
                catch (SharpDX.SharpDXException e)
                {
                    if (e.Message.Contains("WASSTILLDRAWING"))
                        need_retry = true;
                    else
                    {
                        Console.WriteLine("d3d lock surface failed {0}", e.Message);
                        return;
                    }
                }

                if (need_retry != true)
                    break;

                times++;

            } while (times < MAX_TRY);

            if (locked_data.DataPointer == IntPtr.Zero)
            {
                Console.WriteLine("d3d failed to lock offscreen surface");
                return;
            }

            if (data_type_ == DataType.kBGRA)
            {
                for (int i = 0; i < videoFrame.height; i++)
                {
                    CopyMemory(locked_data.DataPointer + locked_data.Pitch * i, videoFrame.yBufferPtr + i * videoFrame.yStride, Math.Min(videoFrame.yStride, locked_data.Pitch));
                }
            }
            else if (data_type_ == DataType.kYUV420)
            {
                IntPtr dst_y_ptr = locked_data.DataPointer;
                for (int i = 0; i < videoFrame.height; i++)
                {
                    CopyMemory(dst_y_ptr + i * locked_data.Pitch, videoFrame.yBufferPtr + i * videoFrame.yStride, Math.Min(videoFrame.yStride, locked_data.Pitch));
                }

                IntPtr dst_u_ptr = dst_y_ptr + locked_data.Pitch * videoFrame.height;
                for (int i = 0; i < videoFrame.height / 2; i++)
                {
                    CopyMemory(dst_u_ptr + i * locked_data.Pitch / 2, videoFrame.vBufferPtr + i * videoFrame.vStride, Math.Min(videoFrame.vStride, locked_data.Pitch / 2));
                }

                IntPtr dst_v_ptr = dst_u_ptr + locked_data.Pitch / 2 * videoFrame.height / 2;
                for (int i = 0; i < videoFrame.height / 2; i++)
                {
                    CopyMemory(dst_v_ptr + i * locked_data.Pitch / 2, videoFrame.uBufferPtr + i * videoFrame.uStride, Math.Min(videoFrame.uStride, locked_data.Pitch / 2));
                }
            }

            _offScreenSurface.UnlockRectangle();
            #endregion // lock offscreen surface

            #region present
            device.Clear(SharpDX.Direct3D9.ClearFlags.Target, new SharpDX.Mathematics.Interop.RawColorBGRA(0, 0, 0, 255), 1.0f, 0);
            device.BeginScene();

            // Stretch the offscreen surface to the backend surface
            device.StretchRectangle(_offScreenSurface, d3d_backend_surface_, TextureFilter.Linear);

            if (d3d_fps_font_ != null)
            {
                string tips_fps = "FPS:" + GetCurrentFps().ToString();
                SharpDX.Mathematics.Interop.RawRectangle font_rect = d3d_fps_font_.MeasureText(null,
                    tips_fps,
                    new SharpDX.Mathematics.Interop.RawRectangle(0, 0, (int)backSurfaceBufferSize.Width, (int)backSurfaceBufferSize.Height),
                    SharpDX.Direct3D9.FontDrawFlags.Center | SharpDX.Direct3D9.FontDrawFlags.VerticalCenter);
                d3d_fps_font_.DrawText(null,
                    tips_fps,
                    (int)(backSurfaceBufferSize.Width - (font_rect.Right - font_rect.Left) - 10),
                    10,
                    new SharpDX.Mathematics.Interop.RawColorBGRA(0, 0, 255, 255));
            }

            device.EndScene();

            // Update D3DImage
            if (_d3dImage != null)
            {
                _d3dImage.Dispatcher.Invoke(() =>
                {
                    _d3dImage.Lock();
                    _d3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, d3d_backend_surface_.NativePointer);
                    _d3dImage.AddDirtyRect(new Int32Rect(0, 0, size_.Width, size_.Height));
                    _d3dImage.Unlock();
                });
            }

            device.Present();
            #endregion // present

            FeedOneFrame();
        }

        public override bool NeedInvoke()
        {
            return false;
        }

        public override void Clear()
        {
            device.Clear(SharpDX.Direct3D9.ClearFlags.Target, new SharpDX.Mathematics.Interop.RawColorBGRA(0, 0, 0, 255), 1.0f, 0);
        }

        private void D3DImage_IsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (_d3dImage.IsFrontBufferAvailable)
            {
                _d3dImage.Lock();
                _d3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, d3d_backend_surface_.NativePointer);
                _d3dImage.Unlock();
            }
        }
        #endregion // override BaseRender

        #region base d3d functions
        private bool EnumValidAdapter(ref int adapter_index, ref SharpDX.Direct3D9.Format adapter_format, ref SharpDX.Direct3D9.DeviceType device_type)
        {
            const int D3DADAPTER_DEFAULT = 0;

            SharpDX.Direct3D9.DisplayMode mode = d3d_.GetAdapterDisplayMode(D3DADAPTER_DEFAULT);
            if (mode.Format == SharpDX.Direct3D9.Format.X8R8G8B8 &&
                !d3d_.CheckDeviceType(D3DADAPTER_DEFAULT, SharpDX.Direct3D9.DeviceType.Hardware, mode.Format, mode.Format, true))
            {
                Console.WriteLine("d3d check device type failed");
            }

            adapter_format = mode.Format;

            adapter_index = D3DADAPTER_DEFAULT;
            device_type = SharpDX.Direct3D9.DeviceType.Hardware;

            foreach (var adapter in d3d_.Adapters)
            {
                var identifier = d3d_.GetAdapterIdentifier(adapter.Adapter);
                if (identifier.Description.Contains("PerfHUD"))
                {
                    adapter_index = adapter.Adapter;
                    device_type = SharpDX.Direct3D9.DeviceType.Reference;
                    break;
                }
            }

            var adapter_identifier = d3d_.GetAdapterIdentifier(adapter_index);
            Console.WriteLine("d3d decide to use adapter: {0} \r\n" +
                " Description: {1}\r\n" +
                " Driver: {2} \r\n" +
                " VendorId: {3}\r\n" +
                " DeviceId: {4}\r\n",
                adapter_index,
                adapter_identifier.Description,
                adapter_identifier.Driver,
                adapter_identifier.VendorId,
                adapter_identifier.DeviceId);

            return true;
        }

        private bool AutoCreateD3DDevice(int frame_width, int frame_height, int wnd_width, int wnd_height)
        {
            if (backSurfaceBufferSize.Width == wnd_width && backSurfaceBufferSize.Height == wnd_height &&
                offScrrenBufferSize.Width == frame_width && offScrrenBufferSize.Height == frame_height)
                return true;

            if (d3d_fps_font_ != null)
                d3d_fps_font_.Dispose();

            if (_offScreenSurface != null)
                _offScreenSurface.Dispose();

            if (d3d_backend_surface_ != null)
                d3d_backend_surface_.Dispose();

            if (device != null)
                device.Dispose();

            int adapter_index = -1;
            SharpDX.Direct3D9.Format adapter_format = SharpDX.Direct3D9.Format.X8R8G8B8;
            SharpDX.Direct3D9.DeviceType device_type = SharpDX.Direct3D9.DeviceType.Hardware;

            if (!EnumValidAdapter(ref adapter_index, ref adapter_format, ref device_type) ||
                adapter_index == -1)
            {
                Console.WriteLine("d3d enum valid adapter failed with index: {0}", adapter_index);
                return false;
            }

            SharpDX.Direct3D9.PresentParameters present_params = GetPresentParameters(frame_width, frame_height, adapter_format);

            device = new SharpDX.Direct3D9.Device(d3d_, adapter_index, device_type, IntPtr.Zero,
                SharpDX.Direct3D9.CreateFlags.Multithreaded | SharpDX.Direct3D9.CreateFlags.SoftwareVertexProcessing,
                present_params);

            SharpDX.Direct3D9.FontDescription font_description = new SharpDX.Direct3D9.FontDescription()
            {
                Height = 24,
                Italic = false,
                CharacterSet = SharpDX.Direct3D9.FontCharacterSet.Ansi,
                FaceName = "Arial",
                MipLevels = 0,
                OutputPrecision = SharpDX.Direct3D9.FontPrecision.TrueType,
                PitchAndFamily = SharpDX.Direct3D9.FontPitchAndFamily.Default,
                Quality = SharpDX.Direct3D9.FontQuality.ClearType,
                Weight = SharpDX.Direct3D9.FontWeight.Thin
            };

            d3d_fps_font_ = new SharpDX.Direct3D9.Font(device, font_description);

            _offScreenSurface = SharpDX.Direct3D9.Surface.CreateOffscreenPlain(device,
                frame_width,
                frame_height,
                data_type_ == DataType.kBGRA ? SharpDX.Direct3D9.Format.X8R8G8B8 : (SharpDX.Direct3D9.Format)842094169 /* MAKEFOURCC('Y', 'V', '1', '2') */,
                SharpDX.Direct3D9.Pool.Default);

            this.texture = new Texture(this.device, wnd_width, wnd_height, 1, Usage.RenderTarget, adapter_format, Pool.Default);
            d3d_backend_surface_ = this.texture.GetSurfaceLevel(0);

            //device.SetRenderState(SharpDX.Direct3D9.RenderState.CullMode, 1 /* D3DCULL_NONE = 1 */);
            device.SetRenderState(RenderState.CullMode, Cull.None);
            device.SetRenderState(SharpDX.Direct3D9.RenderState.Lighting, false);
        
           device.SetRenderState(RenderState.ZEnable, ZBufferType.DontUseZBuffer);
           device.SetRenderState(RenderState.DitherEnable, true);
           device.SetRenderState(RenderState.MultisampleAntialias, true);
           device.SetRenderState(RenderState.AlphaBlendEnable, true);
           device.SetRenderState(RenderState.SourceBlend, Blend.SourceAlpha);
           device.SetRenderState(RenderState.DestinationBlend, Blend.InverseSourceAlpha);
           device.SetSamplerState(0, SamplerState.MagFilter, TextureFilter.Linear);
           device.SetSamplerState(0, SamplerState.MinFilter, TextureFilter.Linear);
           device.SetSamplerState(0, SamplerState.AddressU, TextureAddress.Clamp);
           device.SetSamplerState(0, SamplerState.AddressV, TextureAddress.Clamp);
           device.SetTextureStageState(0, TextureStage.ColorOperation, TextureOperation.SelectArg1);
           device.SetTextureStageState(0, TextureStage.ColorArg1, TextureArgument.Texture);
           device.SetTextureStageState(0, TextureStage.ColorArg2, TextureArgument.Specular);
           device.SetTextureStageState(0, TextureStage.AlphaOperation, TextureOperation.Modulate);
           device.SetTextureStageState(0, TextureStage.AlphaArg1, TextureArgument.Texture);
            device.SetTextureStageState(0, TextureStage.AlphaArg2, TextureArgument.Diffuse);

            offScrrenBufferSize.Width = frame_width;
            offScrrenBufferSize.Height = frame_height;

            backSurfaceBufferSize.Width = wnd_width;
            backSurfaceBufferSize.Height = wnd_height;

            return true;
        }
        #endregion // base d3d functions
    }
}